﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel;
using WcfContrib.Extensions.Context;
using WcfContrib.Extensions.OutputControllers;
using WcfContrib.Helpers;
using WcfContrib.Internal;

namespace WcfContrib.Extensions.Inspectors
{
    class MessageTraceInspector : OutputInspectorBase
    {
        internal const int DefaultDurationLimitMs = 1000 * 5;
        internal const ActivationOption DefaultActivationOption = ActivationOption.UriMatch;
        internal const ParameterDetail DefaultParameterDetail = ParameterDetail.SerializeOnFaultReply;
        internal const int DefaultParameterSerializationDepth = 2;
        const string UriMatch = "trace";

        public ParameterDetail ParameterDetail { get; set; }
        public int ParameterSerializationDepth { get; set; }
        public ActivationOption ActivationOption { get; set; }
        public int DurationLimitMs { get; set; }

        public MessageTraceInspector()
            : this(DefaultActivationOption, DefaultDurationLimitMs, DefaultParameterDetail, DefaultParameterSerializationDepth)
        {
        }

        public MessageTraceInspector(ActivationOption activationOption, int durationLimitMs, ParameterDetail parameterDetail, int parameterSerializationDepth)
        {
            DurationLimitMs = durationLimitMs;
            ActivationOption = activationOption;
            ParameterSerializationDepth = parameterSerializationDepth;
            ParameterDetail = parameterDetail;
        }

        bool IsRelevant(ActivationContextExtension context)
        {
            return ActivationOption == ActivationOption.Enabled
                    || (ActivationOption == ActivationOption.UriMatch && context != null
                        && MessageHelper.UriContainsSegment(context.ToHeader, UriMatch));
        }

        public override string BuildRequestMessageOutput(Message request,
            out object correlationState, out bool processMessage,
            out Dictionary<OutputArgument, object> outputState)
        {
            outputState = null;

            ActivationContextExtension context = ActivationContextExtension.Current;
            processMessage = IsRelevant(context);

            string output = string.Empty;
            if (processMessage)
            {
                SetBasicSettings(out outputState);
                outputState[OutputArgument.ConsoleColor] = ConsoleColor.Green;

                output = string.Format(
                    "(MessageTrace) {0:HH:mm:ss.fff}: Request for action {1} on service {3} by {2}",
                    DateTime.Now, context.Action, context.IdentityName, context.ServiceName);

                correlationState = DateTime.Now;
            }
            else
            {
                correlationState = false;
            }

            return output;
        }

        public override string BuildReplyMessageOutput(Message reply,
            object correlationState, out bool processMessage, out Dictionary<OutputArgument, object> outputState)
        {
            if (correlationState is bool)
            {
                outputState = null;
                processMessage = false;

                return null;
            }

            processMessage = true;
            TimeSpan elapsed = DateTime.Now - (DateTime)correlationState;

            SetBasicSettings(out outputState);
            outputState[OutputArgument.ConsoleColor] = ConsoleColor.Green;

            ActivationContextExtension context = ActivationContextExtension.Current;

            string parameters = BuildParametersOutput(context, reply, true, ParameterDetail, ParameterSerializationDepth);

            string output = string.Format(
                    "(MessageTrace) {0:HH:mm:ss.fff}: Reply for action {1} on service {3} by {2} | Parameters: {5} | Elapsed Time: {4}",
                    DateTime.Now, context.Action, context.IdentityName, context.ServiceName, elapsed, parameters);

            if (reply != null)
            {
                if (reply.IsFault)
                {
                    outputState[OutputArgument.ConsoleColor] = ConsoleColor.Yellow;
                }

                output = string.Format("{0} | IsFault - {1}", output, reply.IsFault);
            }
            else
            {
                output += " | Reply is null";
            }

            if (elapsed.TotalMilliseconds > DurationLimitMs)
            {
                outputState[OutputArgument.ConsoleColor] = ConsoleColor.Red;
                output += " | EXCEEDED DURATION LIMIT";
            }

            return output;
        }

        string BuildParametersOutput(ActivationContextExtension context, Message message, bool isReply, ParameterDetail detail, int depth)
        {
            string parameters = "(";
            
            int idx = 0;
            bool serialize = false;
            if (context.Parameters != null && context.Parameters.Length > 0)
            {
                switch (ParameterDetail)
                {
                    case ParameterDetail.None:
                        parameters += "no detail";
                        break;

                    case ParameterDetail.Existence:
                        foreach (var parameter in context.Parameters)
                        {
                            parameters += string.Format("{0}: {1},", idx++,
                                parameter == null ? "null" : "value");
                        }
                        break;

                    case ParameterDetail.SerializeOnFaultReply:
                        serialize = (isReply && message != null && message.IsFault);
                        if (!serialize)
                        {
                            parameters += "no fault";
                        }
                        break;

                    case ParameterDetail.Serialize:
                        serialize = true;
                        break;
                }
            }

            if (serialize)
            {
                foreach (var parameter in context.Parameters)
                {
                    parameters += string.Format("{0} - {1},", idx++,
                        parameter == null
                        ? "null"
                        : string.Format("{0}: {1}", parameter.GetType().Name,
                            parameter.DumpToString(ParameterSerializationDepth)));
                }
            }

            return parameters + ")";
        }
    }
}
